package biolean.commands;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import biolean.Biolean;
import biolean.Utils;
import biolean.files.BioFile;
import biolean.logic.GraphicElement;
import biolean.logic.Pair;
import biolean.logic.Project;

	//=====Command members======
	//=====Command Constuctors======
	//=====Command methods======



public abstract class Command extends GraphicElement
{
	
	private class TypePair extends Pair<BioFile.IOTYPE, Class<?>>{
		public TypePair(BioFile.IOTYPE first, Class<?> second) {super(first, second);}
	};
	
	//=====Command members======
	protected String mName;
	protected String mDescription;
	protected HashMap<TypePair, Integer>   mAllowedFileTypes = new HashMap<TypePair, Integer>();
	protected List<Pair<String ,Object>> mArguments = new ArrayList<Pair<String ,Object>>();
	
	/*protected boolean mActive = false;
	public void setActivet(boolean active){mActive = active;};
	public boolean getActive(){return mActive;}*/
	
/*	
	public Command()
	{}
*/
	
	public Class<?>[] getAllowedFileTypes()
	{
		Class<?>[] allowedFiles;
		int i = 0;
		
		for (TypePair pair : mAllowedFileTypes.keySet() )
		{
			if (pair.first == BioFile.IOTYPE.OUTPUT)
			{
				i++;
			}
		}
		
		allowedFiles = new Class[i];
		
		i = 0;
		
		for (TypePair pair : mAllowedFileTypes.keySet() )
		{
			if (pair.first == BioFile.IOTYPE.OUTPUT)
			{
				allowedFiles[i] = pair.second;
				i++;
			}
		}
		return allowedFiles;
	}	
	
	public String getName() {
		return mName;
	}
	

	/**
	 * @param bioFileType - Typ suboru na vlozenie, zdena trieda od BioFile
	 * @param ioType - BioFile.Type.Input, BioFile.Type.OutPut
	 * @param count - kolko takych suborov mozem vlozit do commandu.
	 */
	public void insertAllowedFileType(Class<?> bioFileType, BioFile.IOTYPE ioType , int count)
	{ 
		mAllowedFileTypes.put(new TypePair(ioType, bioFileType), count);
	}
		
	
	public void pushFileType( BioFile file )
	{
		TypePair key = new TypePair( file.getIOType() , file.getClass() );
		mAllowedFileTypes.put( key , mAllowedFileTypes.get(key) - 1);
	}	
	
	public void popFileType( BioFile file )
	{
		TypePair key = new TypePair( file.getIOType() , file.getClass() );
		mAllowedFileTypes.put( key , mAllowedFileTypes.get(key) + 1);
	}	
	
	public boolean canConnect(BioFile file)
	{
		TypePair key = new TypePair(file.getIOType(), file.getClass() ) ;
		return !mInPuts.contains(file) && !mConnection.contains( file )  && mAllowedFileTypes.containsKey(key) && mAllowedFileTypes.get(key) > 0;
	}
	
	/**
	 * @param file - pripoji subor k commandu, a zaroven pripoji command k suboru, pripaja dovtedy kym moze,
	 * ak budem mat 3 povolene vstupne sekvecne subory, funkcia moze vratit 3x za sebou true, 4x vrati false
	 */
	public boolean connectFile(BioFile file)
	{
		if (!isConnected(file) && canConnect(file) && file.getInputs().size() == 0 )
		{	
			pushFileType(file);
			mConnection.add(file);
			file.getInputs().add(this);
			return true;
		}
		return false;
	}
	
	public boolean disconnectFile(BioFile file)
	{
		if ( isConnected(file) )
		{
			popFileType(file);
			mConnection.remove(file);
			file.getInputs().remove(this);
			return true;
		}
		return false;
	}

	public void draw(Graphics g) {
		Color color;
		Graphics2D g2 = (Graphics2D) g;
	
		if (mSelected ){
			color = Color.MAGENTA;
		}else if (getActive() ) {
			color = Color.BLUE;
		}else {
			color = Color.BLACK;
		}	

		g2.setColor(color);
		g2.setStroke(ELEMENT_LINE_WIDTH);
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2.drawOval(x - COMMAND_WIDTH/2 , y - COMMAND_HEIGHT/2, COMMAND_WIDTH, COMMAND_HEIGHT);	
		FontMetrics fm = g2.getFontMetrics(BOLD_FONT);
		g2.setFont(BOLD_FONT);
		int textWidth = fm.stringWidth(mName);
    	g2.drawString(mName, x - textWidth / 2, y  - 30 );
    	g2.setFont(NORMAL_FONT);
      }

	public boolean isReady()
	{
		if (mInPuts.size() == 0)
		{
			return false;
		}
		for (GraphicElement file : mInPuts)
		{
			if ( !file.isReady() )
				return false;
		}
		return true;
	}

	public void initialize(int x, int y, String commandName)
	{
		this.x = x;
		this.y = y;
		this.mName = commandName;
	};

	public void saveArguments(Element cmdElement, Document doc)
	{
		for (Pair<String, Object> entry : mArguments )
		{
			Element el = doc.createElement(Project.XML_ARG);
			Element agrumentName = doc.createElement(Project.XML_NAME);
			Element value = doc.createElement(Project.XML_VAL);
			agrumentName.setTextContent( entry.first );
			value.setTextContent( entry.second == null ? Project.XML_NULL :entry.second.toString() );
			el.appendChild(agrumentName);
			el.appendChild(value);
			cmdElement.appendChild(el);
		}
	}
	
	public void addToXML(Document doc , Element root) throws ParserConfigurationException, TransformerException
	{
		processArguments();
		Element command = doc.createElement(Project.XML_COMMAND);
		Attr xAttb = doc.createAttribute(Project.XML_X);
		Attr yAttb = doc.createAttribute(Project.XML_Y);
		Attr typeAttr = doc.createAttribute(Project.XML_TYPE);	
		xAttb.setNodeValue( Integer.toString( this.x) );
		yAttb.setNodeValue( Integer.toString( this.y) );
		typeAttr.setNodeValue( Biolean.cmdRegister.findCommandName(this.getClass() ) );
		
		command.setAttributeNode(xAttb);
		command.setAttributeNode(yAttb);
		command.setAttributeNode(typeAttr);
		
		
		
		Element name = doc.createElement(Project.XML_NAME);
		name.setTextContent(mName);
		Element description = doc.createElement(Project.XML_DESC);
		description.setTextContent(mDescription);

		
		
		command.appendChild(name);
		command.appendChild(description);
		saveArguments(command, doc);
		root.appendChild(command);
	}
		
	public static GraphicElement loadFromXML(Node cmdNode)
	{
		Command result = null;
		
		NamedNodeMap nodeMap = cmdNode.getAttributes();
		int x = Integer.parseInt( nodeMap.getNamedItem(Project.XML_X).getTextContent() );
		int y = Integer.parseInt( nodeMap.getNamedItem(Project.XML_Y).getTextContent() );
		String name = "";
		String type = nodeMap.getNamedItem(Project.XML_TYPE).getTextContent();
		Class<?> cls = Biolean.cmdRegister.findClass(type);
		if (cls != null)
		{
			try {
				result =  (Command)cls.newInstance();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		else
		{
			return null;
		}
		
		NodeList nodeList = cmdNode.getChildNodes();
		name = Utils.findNode(nodeList, Project.XML_NAME).getTextContent();
		result.mDescription = Utils.findNode( nodeList, Project.XML_NAME ).getTextContent();;
		result.initialize(x, y, name);
		List<Node> arguments = Utils.findNodes( nodeList , Project.XML_ARG );
		for (Node node : arguments)
		{
			String argName  =  Utils.findNode(node.getChildNodes(), Project.XML_NAME).getTextContent();
			argName = argName.equals("null") ? null : argName;
			String argValue =  Utils.findNode(node.getChildNodes(), Project.XML_VAL).getTextContent();
			argValue = argValue.equals("null") ? null : argValue;
			result.loadArgument(argName, argValue);
		}
		return result;
	}
	
	
	public abstract void loadArgument(String name, String value);
	public abstract void processArguments();
	public abstract void run() throws IOException;
	public abstract void performDoubleClick();
	public static  String getDescription() { return ""; };
}

